<?php


namespace EchoPHP\File;

use EchoPHP\File\Exception\CannotWriteFileException;
use EchoPHP\File\Exception\ExtensionFileException;
use EchoPHP\File\Exception\FormSizeFileException;
use EchoPHP\File\Exception\IniSizeFileException;
use EchoPHP\File\Exception\NoFileException;
use EchoPHP\File\Exception\NoTmpDirFileException;
use EchoPHP\File\Exception\PartialFileException;
use Symfony\Component\Mime\MimeTypes;
use EchoPHP\File\Exception\FileException;

/**
 * 已上传文件类
 * Class UploadedFile
 * @package EchoPHP\File
 */
class UploadedFile extends File {

    private $originalName;
    private $mimeType;
    private $error;

    public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null) {
        $this->originalName = $this->getName($originalName);
        $this->mimeType = $mimeType ?: 'application/octet-stream';

        $this->error = $error ?: UPLOAD_ERR_OK;
        parent::__construct($path, UPLOAD_ERR_OK === $this->error);
    }

    /**
     * 获取原始文件名
     * @return bool|mixed|string
     */
    public function getClientOriginalName() {
        return $this->originalName;
    }

    /**
     * 获取原始文件后缀
     * @return mixed
     */
    public function getClientOriginalExtension() {
        return pathinfo($this->originalName, PATHINFO_EXTENSION);
    }

    /**
     * 获取文件类型
     * @return string
     */
    public function getClientMimeType() {
        return $this->mimeType;
    }

    /**
     * 获取文件真实后缀
     * @return string|null
     */
    public function guessClientExtension() {
        return MimeTypes::getDefault()->getExtensions($this->getClientMimeType())[0] ?? null;
    }

    /**
     * 获取错误
     * @return int
     */
    public function getError() {
        return $this->error;
    }

    /**
     * 判断文件是否上传成功
     * @return bool
     */
    public function isValid() {
        return UPLOAD_ERR_OK === $this->error && is_uploaded_file($this->getPathname());
    }

    /**
     * 文件移动，通过 move_uploaded_file 移动http上传的文件
     * @param $directory
     * @param null $name
     * @return File
     * @throws \Exception
     */
    public function move($directory, $name = null) {
        if ($this->isValid()) {
            $target = $this->getTargetFile($directory, $name);

            set_error_handler(function ($type, $msg) use (&$error) {
                $error = $msg;
            });
            $moved = move_uploaded_file($this->getPathname(), $target);
            restore_error_handler();
            if (!$moved) {
                throw new FileException(sprintf('无法将文件从 "%s" 移动到 "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
            }

            @chmod($target, 0666 & ~umask());

            return $target;
        }

        switch ($this->error) {
            case UPLOAD_ERR_INI_SIZE:
                throw new IniSizeFileException($this->getErrorMessage());
            case UPLOAD_ERR_FORM_SIZE:
                throw new FormSizeFileException($this->getErrorMessage());
            case UPLOAD_ERR_PARTIAL:
                throw new PartialFileException($this->getErrorMessage());
            case UPLOAD_ERR_NO_FILE:
                throw new NoFileException($this->getErrorMessage());
            case UPLOAD_ERR_NO_TMP_DIR:
                throw new NoTmpDirFileException($this->getErrorMessage());
            case UPLOAD_ERR_CANT_WRITE:
                throw new CannotWriteFileException($this->getErrorMessage());
            case UPLOAD_ERR_EXTENSION:
                throw new ExtensionFileException($this->getErrorMessage());
        }

        throw new FileException($this->getErrorMessage());
    }

    /**
     * 文件复制 通过http上传的文件只能以移动的形式
     * @param $directory
     * @param null $name
     * @return File
     * @throws \Exception
     */
    public function copy($directory, $name = null) {
        return $this->move($directory, $name);
    }

    /**
     * 获取php定义的最大上传文件大小
     * @return int|string
     */
    public static function getMaxFileSize() {
        $iniMax = strtolower(ini_get('upload_max_filesize'));

        $max = ltrim($iniMax, '+');
        if (0 === strpos($max, '0x')) {
            $max = \intval($max, 16);
        } elseif (0 === strpos($max, '0')) {
            $max = \intval($max, 8);
        } else {
            $max = (int)$max;
        }

        switch (substr($iniMax, -1)) {
            //不添加break语句，让其直接执行后续乘法得到 最后 定义的字节数
            case 't':
                $max *= 1024;
            case 'g':
                $max *= 1024;
            case 'm':
                $max *= 1024;
            case 'k':
                $max *= 1024;
        }

        return $max;
    }

    /**
     * 获取错误信息
     * @return string
     */
    public function getErrorMessage() {
        static $errors = [
            UPLOAD_ERR_OK => '文件 "%s" 上传成功',
            UPLOAD_ERR_INI_SIZE => '文件 "%s" 超出了INI文件中配置的最大值 (配置值 %d KiB)',
            UPLOAD_ERR_FORM_SIZE => '文件 "%s" 超出了表单定义的最大值',
            UPLOAD_ERR_PARTIAL => '文件 "%s" 只有部分上传',
            UPLOAD_ERR_NO_FILE => '没有文件被上传',
            UPLOAD_ERR_CANT_WRITE => '文件 "%s" 写入失败',
            UPLOAD_ERR_NO_TMP_DIR => '文件无法上传: 没有临时文件夹',
            UPLOAD_ERR_EXTENSION => '一个PHP扩展阻止了文件上传',
        ];

        $errorCode = $this->error;
        $maxFileSize = UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0;
        $message = isset($errors[$errorCode]) ? $errors[$errorCode] : '由于未知错误，文件 "%s" 上传失败';

        return sprintf($message, $this->getClientOriginalName(), $maxFileSize);
    }

}